package com.xscha.soyue.common.core.utils;

import cn.hutool.core.util.ArrayUtil;
import com.xscha.soyue.common.core.exception.SoyRuntimeException;
import com.xscha.soyue.common.core.exception.status.CommonStatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;

import java.lang.reflect.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * class utils
 *
 * @author Vondser
 * @version V2024.1.0
 */
@Slf4j
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ClassUtils {

	private final static ResourcePatternResolver RESOLVER = new PathMatchingResourcePatternResolver();

	private final static MetadataReaderFactory META_READER = new CachingMetadataReaderFactory();

	public static boolean notAbstractAndInterface(Class<?> type) {
		return !Modifier.isInterface(type.getModifiers()) && !Modifier.isAbstract(type.getModifiers());
	}

	public static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... paramTypes) {
		Assert.notNull(clazz, "Class must not be null");
		try {
			return clazz.getConstructor(paramTypes);
		}
		catch (NoSuchMethodException ex) {
			throw new SoyRuntimeException(ex);
		}
	}

	public static Set<Class<?>> scan(Predicate<Class<?>> predicate, String... bases) {
		if (ArrayUtil.isEmpty(bases)) {
			throw new SoyRuntimeException(CommonStatusCode.ARG_INVALID, "参数错误，bases不能为空");
		}
		return Arrays.stream(bases)
			.filter(Objects::nonNull)
			.map(base -> scan(base, predicate))
			.collect(Collector.of(HashSet::new, AbstractCollection::addAll, (r1, r2) -> {
				r1.addAll(r2);
				return r1;
			}));
	}

	public static Set<Class<?>> scan(String base, Predicate<Class<?>> predicate) {
		String basePack = "classpath*:" + base.replace(".", "/") + "/**/*.class";
		try {
			Resource[] resources = RESOLVER.getResources(basePack);
			return Arrays.stream(resources).map(resource -> {
				try {
					MetadataReader reader = META_READER.getMetadataReader(resource);
					String className = reader.getClassMetadata().getClassName();
					if (log.isDebugEnabled()) {
						log.debug("扫描类:{}", className);
					}
					return Class.forName(className);
				}
				catch (Throwable e) {
					log.error("类[{}]不存在,[{}]", resource, e.getMessage());
					return null;
				}
			}).filter(Objects::nonNull).filter(predicate).collect(Collectors.toSet());
		}
		catch (Throwable e) {
			log.error("扫描异常:[{}]", e.getMessage());
			return Set.of();
		}
	}

	public static Optional<Type> findGeneric(Class<?> realType, int index) {
		return findGeneric(realType, realType, index);
	}

	/**
	 * 查找泛型
	 */
	public static Optional<Type> findGeneric(Class<?> realType, Class<?> destType, int index) {
		List<Type> types = findGeneric(realType, destType);
		return types.size() > index ? Optional.ofNullable(types.get(index)) : Optional.empty();
	}

	public static List<Type> findGeneric(Class<?> realType) {
		return findGeneric(realType, realType);
	}

	public static List<Type> findGeneric(Class<?> realType, Class<?> destType) {
		List<Type> parameterClasses;
		// conditions
		TypeVariable[] params = destType.getTypeParameters();
		if (params.length > 0) { // parameters have to be present
			parameterClasses = new ArrayList<>(params.length);
			// to keep track of where parameters go in the class hierarchy
			Map<TypeVariable, Type> varsPrevious = new HashMap<>(3);
			Map<TypeVariable, Type> varsCurrent = new HashMap<>(3);
			Type parent = realType;
			while ((parent = getGenericSuperType((Class) parent, destType)) != null) {
				if (parent instanceof ParameterizedType) {
					ParameterizedType pa = (ParameterizedType) parent;
					TypeVariable[] pars = getClass(pa.getRawType()).getTypeParameters();
					int i = 0;
					for (Type t : pa.getActualTypeArguments()) {
						if (t instanceof TypeVariable) {
							varsCurrent.put(pars[i++], varsPrevious.getOrDefault(t, null));
						}
						else {
							varsCurrent.put(pars[i++], t);
						}
					}
					parent = pa.getRawType();
				}
				varsPrevious.clear();
				varsPrevious.putAll(varsCurrent);
				varsCurrent.clear();
			}
			for (TypeVariable tv : params) {
				parameterClasses.add(varsPrevious.get(tv));
			}
		}
		else {
			parameterClasses = List.of();
		}
		return parameterClasses;
	}

	/**
	 * @param type
	 * @return
	 */
	public static Class<?> getClass(Type type) {
		if (type instanceof Class) {
			return (Class<?>) type;
		}
		else if (type instanceof ParameterizedType) {
			return (Class<?>) ((ParameterizedType) type).getRawType();
		}
		else if (type instanceof GenericArrayType) {
			Type componentType = ((GenericArrayType) type).getGenericComponentType();
			Class<?> componentClass = getClass(componentType);
			if (componentClass != null) {
				return Array.newInstance(componentClass, 0).getClass();
			}
			else {
				return null;
			}
		}
		else if (type instanceof TypeVariable) {
			return null;
		}
		else if (type instanceof WildcardType) {
			return null;
		}
		else {
			return null;
		}
	}

	/**
	 * @param realType
	 * @param destType
	 * @param <T>
	 * @return
	 */
	public static <T> Type getGenericSuperType(Class<? extends T> realType, Class<? extends T> destType) {
		if (realType.getSuperclass() != null && destType.isAssignableFrom(realType.getSuperclass())) {
			return realType.getGenericSuperclass();
		}
		else {
			int i = 0;
			for (Class in : realType.getInterfaces()) {
				if (destType.isAssignableFrom(in)) {
					return realType.getGenericInterfaces()[i];
				}
				i++;
			}
			return null;
		}
	}

}
